package demo01

import (
	"fmt"
	"sync"
	"time"
)

// 3. sync.Lock
/*
说到并发编程，就不得不谈一个老生常谈的问题，那就是资源竞争，也就是我们这节要讲的并发安全。因为一旦开启了多个goroutine去处理问题，
那么这些goroutine就有可能在同一时间操作同一个系统资源，比如同一个变量，同一份文件等等，这里我们如果不加控制的话，可能会出现并发安全问题，
在Go语言中，有两种方式来控制并发安全，锁和原子操作 举个例子，看下面代码
*/

var (
	num int
	wg2 = sync.WaitGroup{}
	// 我们用锁来保证num的并发安全
	mu = sync.Mutex{}
	// 读写锁测试变量
	cnt = 0
)

// LockDemo01
/*
我们用n（这里是10000，可以自行修改，尽量数字大一点）个goroutine去给num做累加，最后并num并不等于n，这就是并发问题，同一时间有多个goroutine都在对num做+1操作，
但是后一个并不是在前一次执行完的基础之上运行的，可能两次运行num的初始相同，这样前一个num+1的结果就被后一个覆盖了，看起来好像只做了一个加法。
为了避免类似的并发安全问题，我们一般会采用下面两种方式处理，在Go语言中并发相关的都在sync包下面。
*/

func add() {
	defer wg2.Done()
	num += 1
}
func LockDemo01() {
	var n = 10 * 10 * 10 * 10
	wg2.Add(n)
	for i := 0; i < n; i++ {
		// 启动n个goroutine去累加num
		go add()
	}
	// 等待所有goroutine执行完毕
	wg2.Wait()
	// 不出意外的话，num应该等于n，但是，但是，但是实际上不一致！
	fmt.Println(num == n)
}

func add2() {
	defer wg2.Done()
	mu.Lock()
	num += 1
	mu.Unlock()
}

// LockDemo02
/*
3.1 锁
3.1.1 互斥锁 Mutex
互斥锁是一种最常用的控制并发安全的方式，它在同一时间只允许一个goroutine对共享资源进行访问
互斥锁的声明方式如下：
var lock sync.Mutex
互斥锁有两个方法
func (m *Mutex) Lock()     // 加锁
func (m *Mutex) Unlock()   // 解锁

我们可以自行修改n的值，在我们能开启足够多的goroutine 的情况下，他结果一定会是true
本例使用了前面介绍的sync.WaitGroup来等待所有协程执行结束,并且在add函数里使用了互斥锁来保证num += 1操作的并发安全，
但是注意不要忘了用mu.Unlock来进行解锁，否则其他goroutine将一直等待加锁造成阻塞
*/
func LockDemo02() {
	var n = 10 * 10 * 10 * 10
	wg2.Add(n)
	for i := 0; i < n; i++ {
		// 启动n个goroutine去累加num
		go add2()
	}
	// 等待所有goroutine执行完毕
	wg2.Wait()
	// 不出意外的话，num应该等于n，但是，但是，但是实际上不一致！
	fmt.Println(num == n)
}

// RWLockDemo 3.1.2 读写锁 RWMutex
/*
顾名思义，读写锁就是将读操作和写操作分开，可以分别对读和写进行加锁，一般用在大量读操作、少量写操作的情况
func (rw *RWMutex) Lock()     // 对写锁加锁
func (rw *RWMutex) Unlock()   // 对写锁解锁

func (rw *RWMutex) RLock()    // 对读锁加锁
func (rw *RWMutex) RUnlock()  // 对读锁解锁

读写锁的使用遵循以下几个法则：

同时只能有一个 goroutine 能够获得写锁定
同时可以有任意多个 gorouinte 获得读锁定
同时只能存在写锁定或读锁定（读和写互斥）
通俗理解就是可以多个goroutine同时读，但是只有一个goroutine能写，共享资源要么在被一个或多个goroutine读取，要么在被一个goroutine写入， 读写不能同时进行
*/
func write(mr *sync.RWMutex, i int) {
	fmt.Printf("goroutine%d writer start\n", i)
	mr.Lock()
	cnt++
	fmt.Printf("goroutine%d writing count:%d\n", i, cnt)
	time.Sleep(time.Millisecond)
	mr.Unlock()
	fmt.Printf("goroutine%d writer over\n", i)
}
func read(mr *sync.RWMutex, i int) {
	fmt.Printf("goroutine%d reader start\n", i)
	mr.RLock()
	fmt.Printf("goroutine%d reading count:%d\n", i, cnt)
	time.Sleep(time.Millisecond)
	mr.RUnlock()
}

/*
	RWLockDemo

简单分析：首先goroutine3开始加了读锁，开始读取，读到count的值为0，然后goroutine1尝试写入，goroutine2尝试写入，
但是都会阻塞，因为goroutine3加了读锁，不能再加写锁，在第8行goroutine3 读取完毕之后，goroutine1争抢到了锁，加了写锁，写完释放写锁之后，
goroutine1和goroutine2同时加了读锁，读到count的值为1。可以看到读写锁是互斥的，写写锁是互斥的，读读锁可以一起加
*/
func RWLockDemo() {
	var mr sync.RWMutex
	for i := 0; i < 3; i++ {
		go write(&mr, i)
	}
	for i := 0; i < 3; i++ {
		go read(&mr, i)
	}
	time.Sleep(time.Second)
	fmt.Println("final count:", cnt)
}

/*
	SyncMapDemo

通过store方法写入两个键值对
读取key为age的值，读出来age为18
通过range方法遍历Map的key和value
删除key为age的键值对，删除完之后，再次读取age，age为空，ok为false表Map里没有这个key
LoadOrStore尝试读取key为name的值，读取不到就写入键值对name-zhangsan，能读取到就返回原Map里的name对应的值
注意：sync.Map 没有提供获取Map数量的方法，需要我们在对 sync.Map进行遍历时自行计算，sync.Map 为了保证并发安全有一些性能损失，
因此在非并发情况下，使用Map相比使用 sync.Map 会有更好的性能
*/
func SyncMapDemo() {
	var m sync.Map
	// 1.写入
	m.Store("name", "diaomao")
	m.Store("age", 99)
	// 2.读取
	age, _ := m.Load("age")
	//fmt.Printf("age:%t", age)
	fmt.Println(age.(int))
	//fmt.Printf("age:%t", age)

	// 3. 遍历
	m.Range(func(key, value interface{}) bool {
		fmt.Printf("key is:%v, val is:%v\n", key, value)
		return true
	})

	// 4. 删除
	m.Delete("age")
	age, ok := m.Load("age")
	fmt.Println(age, ok)

	// 5. 读取或写入
	actual, loaded := m.LoadOrStore("title2", "zhangsan")
	//title, _ := m.Load("title")
	fmt.Println(actual)
	fmt.Println(loaded)
}
